package android.slc.commonlibrary.util.compat;

import android.os.Build;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

import com.blankj.utilcode.util.AppUtils;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.SDCardUtils;
import com.blankj.utilcode.util.StringUtils;
import com.blankj.utilcode.util.ThrowableUtils;
import com.blankj.utilcode.util.TimeUtils;
import com.blankj.utilcode.util.Utils;

import java.io.File;

/**
 * <pre>
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/09/27
 *     desc  : utils about crash
 * </pre>
 */
public final class SlcCrashUtils {
    private static final String FILE_SEP = System.getProperty("file.separator");
    private static final Thread.UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();

    private SlcCrashUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * Initialization.
     */
    public static void init() {
        init("");
    }

    /**
     * Initialization
     *
     * @param crashDir The directory of saving crash information.
     */
    public static void init(@NonNull final File crashDir) {
        init(crashDir.getAbsolutePath(), null);
    }

    /**
     * Initialization
     *
     * @param crashDirPath The directory's path of saving crash information.
     */
    public static void init(final String crashDirPath) {
        init(crashDirPath, null);
    }

    /**
     * Initialization
     *
     * @param onCrashListener The crash listener.
     */
    public static void init(final OnCrashListener onCrashListener) {
        init("", onCrashListener);
    }

    /**
     * Initialization
     *
     * @param crashDir        The directory of saving crash information.
     * @param onCrashListener The crash listener.
     */
    public static void init(@NonNull final File crashDir, final OnCrashListener onCrashListener) {
        init(crashDir.getAbsolutePath(), onCrashListener);
    }

    /**
     * Initialization
     *
     * @param crashDirPath    The directory's path of saving crash information.
     * @param onCrashListener The crash listener.
     */
    public static void init(final String crashDirPath, final OnCrashListener onCrashListener) {
        String dirPath;
        if (StringUtils.isSpace(crashDirPath)) {
            if (SDCardUtils.isSDCardEnableByEnvironment()
                    && Utils.getApp().getExternalFilesDir(null) != null)
                dirPath = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + "crash" + FILE_SEP;
            else {
                dirPath = Utils.getApp().getFilesDir() + FILE_SEP + "crash" + FILE_SEP;
            }
        } else {
            dirPath = crashDirPath.endsWith(FILE_SEP) ? crashDirPath : crashDirPath + FILE_SEP;
        }
        new Handler(Looper.getMainLooper()).post(() -> {
            while (true) {
                try {
                    Looper.loop();
                } catch (Throwable throwable) {
                    uncaughtException(dirPath, Thread.currentThread(), throwable, onCrashListener);
                }
            }
        });
        Thread.setDefaultUncaughtExceptionHandler(getUncaughtExceptionHandler(dirPath, onCrashListener));
    }

    private static Thread.UncaughtExceptionHandler getUncaughtExceptionHandler(final String dirPath,
                                                                               final OnCrashListener onCrashListener) {
        return new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(@NonNull final Thread t, @NonNull final Throwable e) {
                SlcCrashUtils.uncaughtException(dirPath, t, e, onCrashListener);
            }
        };
    }

    private static void uncaughtException(final String dirPath, @NonNull final Thread t, @NonNull final Throwable e, final OnCrashListener onCrashListener) {
        final String formatCrashInfo = formatCrashInfo(e);
        final OnNotifyUncaughtExceptionListener onNotifyUncaughtExceptionListener = e1 -> {
            final String crashFile = dirPath + SlcFileUtils.newFileNameByTime("CrashLog_", ".txt");
            FileIOUtils.writeFileFromString(crashFile, formatCrashInfo, true);
            if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
                DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(t, e1);
            }
        };
        if (onCrashListener != null) {
            onCrashListener.onCrash(formatCrashInfo(e), e, onNotifyUncaughtExceptionListener);
        } else {
            onNotifyUncaughtExceptionListener.onNotifyUncaughtException(e);
        }

    }

    public static String formatCrashInfo(@NonNull final Throwable e) {
        final StringBuilder sb = new StringBuilder();
        final String head = "************* Log Head ****************" +
                "\nTime Of Crash      : " + TimeUtils.getNowString() +
                "\nDevice Manufacturer: " + Build.MANUFACTURER +
                "\nDevice Model       : " + Build.MODEL +
                "\nAndroid Version    : " + Build.VERSION.RELEASE +
                "\nAndroid SDK        : " + Build.VERSION.SDK_INT +
                "\nApp VersionName    : " + AppUtils.getAppVersionName() +
                "\nApp VersionCode    : " + AppUtils.getAppVersionCode() +
                "\n************* Log Head ****************\n\n";
        sb.append(head).append(ThrowableUtils.getFullStackTrace(e));
        return sb.toString();
    }
    ///////////////////////////////////////////////////////////////////////////
    // interface
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 异常监听
     */
    public interface OnCrashListener {
        /**
         * 异常监听
         * @param crashInfo 异常信息
         * @param e 异常
         * @param onNotifyUncaughtExceptionListener 通知未处理异常监听
         */
        void onCrash(String crashInfo, Throwable e, OnNotifyUncaughtExceptionListener onNotifyUncaughtExceptionListener);
    }

    /**
     * 通知未处理异常监听
     */
    public interface OnNotifyUncaughtExceptionListener {
        void onNotifyUncaughtException(Throwable e);
    }
}
